package cn.tk.config;

import cn.tk.cache.redis.RedisService;
import cn.tk.exception.CustomException;
import cn.tk.model.pojo.ResultError;
import cn.tk.resource.AppWhiteList;
import cn.tk.utils.EmptyUtil;
import cn.tk.utils.MD5Util;
import cn.tk.utils.RegexUtil;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.TimeUnit;

/**
 * Created by denglw on 2021/5/27.<br/>
 * Desc: 验签拦截器
 */
@Slf4j @Component
public class SignInterceptor implements HandlerInterceptor {

    private static final String NONCE_KEY = "x-nonce-";

    @Autowired
    private RedisService redisService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String appId = request.getHeader("appId");
        String timestamp = request.getHeader("timestamp");
        String sign = request.getHeader("sign");
        String nonce = request.getHeader("nonce");
        int errCode = ResultError.Forbidden.getCode();
        if (EmptyUtil.anyBlank(appId, timestamp, sign, nonce) || !RegexUtil.isMatch(RegexUtil.NUMBER, timestamp)) {
            log.error("请求头参数缺失:appId--[{}];timestamp--[{}];sign--[{}];nonce--[{}].", appId, timestamp, sign, nonce);
            throw new CustomException(errCode, "请求头参数缺失或格式有误.");
        }

        // 校验appId是否存在
        boolean existAppId = AppWhiteList.of().existAppId(appId);
        if (!existAppId) {
            log.error("appId无效.");
            throw new CustomException(errCode, "appId无效.");
        }
        String appSecret = AppWhiteList.of().appSecret(appId);

        // 校验sign是否已过期: 请求时间戳与服务器当前时间戳差值大于120s，则当前请求的timestamp无效
        if ((Math.abs(Long.parseLong(timestamp) - System.currentTimeMillis()) / 1000) > 120){
            log.error("签名已过期.");
            throw new CustomException(errCode, "签名已过期.");
        }

        // 防止重放攻击：通过判断redis中的nonce，确认当前请求是否为重复请求
        String nonceKey = NONCE_KEY + appId + nonce;
        Boolean existNonce = redisService.getStringReadTemplate(nonceKey).hasKey(nonceKey);
        if (existNonce != null && existNonce) {
            log.error("禁止重复请求.");
            throw new CustomException(errCode, "禁止重复请求.");
        }
        // 将nonce设置到缓存: 缓存120s
        StringRedisTemplate writeTemplate = redisService.getStringWriteTemplate(nonceKey);
        writeTemplate.opsForValue().set(nonceKey, nonce);
        writeTemplate.expire(nonceKey, 120L, TimeUnit.SECONDS);

        // 验签
        String encodeSrc = appId + appSecret + timestamp + nonce;
        String signEcrypt = MD5Util.md5(encodeSrc);
        if (!sign.equalsIgnoreCase(signEcrypt)) {
            log.error("签名不一致，验签失败.");
            throw new CustomException(errCode, "签名不一致，验签失败.");
        }
        return true;
    }

    @Data
    @NoArgsConstructor
    static class HeadReq {
        /**
         * appId
         */
        private String appId;

        /**
         * appSecret秘钥
         */
        private String appSecret;

        /**
         * 10位时间戳
         */
        private String timestamp;

        /**
         * 参数签名
         */
        private String sign;

        /**
         * 随机字符串
         */
        private String nonce;
    }

}
